package utils.parser;


import java.io.IOException;
import java.util.LinkedList;
import java.util.HashSet;
import kb.DescriptionLogicsKB;

import utils.exception.OrderingDescriptionException;
import utils.exception.ProjectionException;
import utils.exception.SyntaxParsingException;
import utils.Util;

import algebra.OrderingDescription;
import algebra.ProjectionDescription;
import algebra.op.*;

import entities.Assertion;
import entities.Axiom;
import entities.ConceptAssertion;
import entities.EquivalenceAxiom;
import entities.InclusionAxiom;
import entities.Individual;
import entities.RoleAssertion;
import entities.concept.AndConcept;
import entities.concept.Concept;
import entities.concept.DomainConcept;
import entities.concept.DomainConceptEQ;
import entities.concept.DomainConceptLT;
import entities.concept.DomainConceptLTEQ;
import entities.concept.ExistsConcept;
import entities.concept.NotConcept;
import entities.concept.OrConcept;
import entities.concept.PConcept;
import entities.concept.UniversalConcept;
import entities.role.Role;
import static entities.vocabulary.KBVocabulary.*;
import static entities.vocabulary.QueryKeywords.*;



public class Parser {
	
	private Parser(){
	}
	
	//deal with axioms and convert concepts to GCIs for TBox
	public static Axiom parseAxioms(LinkedList<String> tokens) throws SyntaxParsingException{
		if(tokens.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		String first = tokens.getFirst();
		//test on the tokens
		if(! first.equals( LEFTDEL)){
			System.out.println("Outer brackets expected.");
			throw new SyntaxParsingException();
		}else{
			tokens.remove();
			//remove the last symbol which is ")"
			String test = tokens.removeLast();
			assert test .equals( RIGHTDEL );
		}
		//axiom? remove brackets and parse
		Axiom ax = null;
		if(tokens.peek().equals(IMPLIES)){
			tokens.remove();
			ax = createInclusionAxiom(tokens);
		}else if(tokens.peek().equals(EQAXIOM)){
			//equivalent axiom
			tokens.remove();
			ax = createEqAxiom(tokens);
		}
		else{
			//other cases: into GCI 
			ax = new InclusionAxiom(entities.vocabulary.KBVocabulary.CONCEPT_TOP, conParser(tokens));
		}
		//if (debug) System.out.println(Renderer.renderAxiom(ax));
		return ax;	
	}
	
	public static LinkedList<String> getListOfTokensFromTokenizer(String line){
		Tokenizer t = new Tokenizer(line);
        LinkedList<String> list = new LinkedList<String>();
		while(t.hasNextToken()) {
			String st = null;
			try {
				st = t.nextToken();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			list.add(st);
		}
		return list;
	}
	
	//parse only assertions, ie. concept and role assertions
	public static Assertion parseAssertions(LinkedList<String> tokens) throws SyntaxParsingException{
		Assertion ast = null;
		if(tokens.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		String first = tokens.getFirst();
		if(! first.equals( LEFTDEL)){
			System.out.println("Outer brackets expected.");
			throw new SyntaxParsingException();
		}else{
			tokens.remove();
			//remove the last symbol which is ")"
			String test = tokens.removeLast();
			assert test .equals( RIGHTDEL );
		}
		if(tokens.peek().equals(CONASSERT)){
			tokens.remove();
			ast = createConceptAssertion(tokens);
		}else if(tokens.peek().equals(ROLEASSERT)){
			//equivalent axiom
			tokens.remove();
			ast = createRoleAssertion(tokens);
		}
		return ast;
	}
	
	public static Query parseAlgebraQuery(LinkedList<String> tokens) throws SyntaxParsingException{
		if(tokens.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		Query query = null;
		String first = tokens.getFirst();
		if(first.equals(LEFTDEL)){
			tokens.remove();
			tokens.removeLast();
		}
		first = tokens.poll();
		if(first.equals(QCON)){
			query = new CQuery(conParser(tokens));
		}else if(first.equals(QINFORM)){
			LinkedList<LinkedList<String>> subs =  breakListByMatchingBrackets(tokens);
			if(subs.size()!=2){
				throw new SyntaxParsingException();
			}else{
				//C, Pd
				query = new InformativeObjectQuery( createProjectionDescription(subs.get(1)), 
						new CQuery(conParser(subs.get(0))));
			}
		}else if(first.equalsIgnoreCase(QPROJ)){
			LinkedList<LinkedList<String>> subs =  breakListByMatchingBrackets(tokens);
			if(subs.size()!=2){
				throw new SyntaxParsingException();
			}else{
				//Q, Pd
				query = new ProjectionQuery( createProjectionDescription(subs.get(1)), 
						 parseAlgebraQuery(subs.get(0)));
			}
		}else if(first.equalsIgnoreCase(QINTER)){
			LinkedList<LinkedList<String>> subs =   breakListByMatchingBrackets(tokens);
			if(subs.size()!=2){
				throw new SyntaxParsingException();
			}else{
				query = new IntersectionQuery( parseAlgebraQuery(subs.get(0)), 
						 parseAlgebraQuery(subs.get(1)));
			}
		}else if(first.equalsIgnoreCase(QPI)){
			query =  new PQuery();
		}else if(first.equalsIgnoreCase(QSCAN)){
			//sub-Q
			LinkedList<LinkedList<String>> subs = breakListByMatchingBrackets(tokens);
			if(subs.size() != 1){
				throw new SyntaxParsingException();
			}else{
				query = new ScanQuery( parseAlgebraQuery(subs.get(0)));
			}
		}else if(first.equalsIgnoreCase(QSELECT)){
			//C, Q
			LinkedList<LinkedList<String>> subs =  breakListByMatchingBrackets(tokens);
			if(subs.size()!=2){
				throw new SyntaxParsingException();
			}else{
				query = new SelectionQuery( parseAlgebraQuery(subs.get(1)), 
						conParser(subs.get(0)));
			}
		}else{
			System.out.println("Query type not supported: "+ first);
			throw new SyntaxParsingException();
		}
		return query;
	}
		
	public static OrderingDescription createOrderingDescription(LinkedList<String> tokens, DescriptionLogicsKB kb)
		throws SyntaxParsingException{
		OrderingDescription od = null;
		try {
			od = new OrderingDescription("", kb);
		} catch (OrderingDescriptionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		String head = tokens.peek();
		if(head.equalsIgnoreCase(LEFTDEL)){
			tokens.remove();
			tokens.removeLast();
		}
		head = tokens.poll();
		if(head.equalsIgnoreCase(ORDERING_UN) || head.equalsIgnoreCase("(un)")){
			od.setType(ORDERING_UN);
		}else if(head.equalsIgnoreCase(ORDERING_FEATURE)){
			//f, Od
			LinkedList<LinkedList<String>> subs =  breakListByMatchingBrackets(tokens);
			if(subs.size() != 2){
				System.out.println("(f, Od) expected.");
				throw new SyntaxParsingException();
			}else{
				od.arg =  tokensAsString(subs.get(0));
				od.residualOd =  createOrderingDescription(subs.get(1), kb);
			}
		}else if(head.equalsIgnoreCase(ORDERING_PARTITION)){
			//C (Od1 Od2)
			LinkedList<LinkedList<String>> subs = breakListByMatchingBrackets(tokens);
			if(subs.size() != 2){
				System.out.println("(f, Od) expected.");
				throw new SyntaxParsingException();
			}else{
				od.partition =  conParser(subs.get(0));
				LinkedList<String> ods = subs.get(1);
				if(! ods.peek().equalsIgnoreCase(LEFTDEL)){
					System.out.println("Malformed Partition Ordering Description.");
					throw new SyntaxParsingException();
				}
				ods.remove();
				ods.removeLast();
				subs =  breakListByMatchingBrackets(ods);
				if(subs.size() != 2){
					System.out.println("(f, Od) expected.");
					throw new SyntaxParsingException();
				}else{
					od.residualOd =  createOrderingDescription(subs.get(0), kb);
					od.residualOd2 =  createOrderingDescription(subs.get(1), kb);
				}
			}
		}
		return od;
	}
	
	public static ProjectionDescription createProjectionDescription(LinkedList<String> tokens)
		throws SyntaxParsingException{
		// TODO Auto-generated constructor stub
		String head = tokens.peek();
		if(! head.equalsIgnoreCase(LEFTDEL)){
			System.out.println("Malformed Projection Description: "+head);
			throw new SyntaxParsingException();
		}else{
			tokens.remove();
			tokens.removeLast();
		}
		head = tokens.poll();
		ProjectionDescription pd = null;
		try {
			pd = new ProjectionDescription("");
		} catch (ProjectionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(head.equalsIgnoreCase(PD_CONCEPT)){
			pd.setType(ProjectionDescription.CONCEPT);
			pd.setConcept( conParser(tokens));
		}else if(head.equalsIgnoreCase(PD_CONJUNCTION)){
			pd.setType(ProjectionDescription.CONJUNCTION);
			//pd1, pd2
			LinkedList<LinkedList<String>> subs =  breakListByMatchingBrackets(tokens);
			if(subs.size() != 2){
				System.out.println("Two Pds expected for Pd1 AND Pd2.");
				throw new SyntaxParsingException();
			}else{
				pd.setPd1( createProjectionDescription(subs.get(0)));
				pd.setPd2( createProjectionDescription(subs.get(1)));
			}
		}else if(head.equalsIgnoreCase(PD_FEATURE)){
			pd.setType(ProjectionDescription.FEATURE);
			pd.setArg( tokensAsString(tokens));
		}else if(head.equalsIgnoreCase(PD_ROLE)){
			pd.setType(ProjectionDescription.ROLE);
			//R, Pd1
			LinkedList<LinkedList<String>> subs =  breakListByMatchingBrackets(tokens);
			if(subs.size() != 2){
				System.out.println("R, Pds expected for Exists R.Pd");
				throw new SyntaxParsingException();
			}else{
				pd.setPd1( createProjectionDescription(subs.get(1)));
				pd.setArg( tokensAsString(subs.get(0)));
			}
		}else{
			System.out.println("Porjection Description not supported. Exit. ");
			throw new SyntaxParsingException();
		}
		
		return pd;
	}
	
	public static String tokensAsString(LinkedList<String> in){
		String ret = "";
		while(! in.isEmpty()){
			ret += in.poll()+" ";
		}
		return ret.trim();
	}
	
	//
	public static LinkedList<LinkedList<String>> breakListByMatchingBrackets(LinkedList<String> in){
		LinkedList<LinkedList<String>> res = new LinkedList<LinkedList<String>>();
		if(in.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new RuntimeException();
		}
		String tk = null;
		int count = 0;
		LinkedList<String> sub = new LinkedList<String>();
		while(!in.isEmpty()){
			tk = in.poll();
			
			if(tk.equals(LEFTDEL) ){
				count ++;
			}else if(tk.equals(RIGHTDEL)){
				count --;
			}
			sub.add(tk);
			if(count == 0){
				break;
			}
		}//end of while
		res.add(sub);
		if(in.size()>0){
			res.addAll(breakListByMatchingBrackets(in));
		}
		return res;
	}
	
	private static Assertion createRoleAssertion(LinkedList<String> in) throws SyntaxParsingException {	
		if(in.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		Role r = null;
		Individual a = null, b=null;
		int count = 0;
		boolean done = false;
		int ind = 0;
		String tk = null;
		LinkedList<String> sub = new LinkedList<String>();
		while(!in.isEmpty()){
			tk = in.remove();
			if(tk.equals(LEFTDEL) ){
				count ++;
			}else if(tk.equals(RIGHTDEL)){
				count --;
			}
			switch (ind){
			case 0:
				a = Individual.newIndividual(tk);
				//System.out.println("creates ind:"+tk);
				ind++;
				break;
			case 1:
				b = Individual.newIndividual(tk);
				//System.out.println("creates ind:"+tk);
				ind++;
				break;
			case 2:
				if(count != 0){
					sub.add(tk);
					sub.addAll(in);
					done = true;
				}else{
					System.err.println("Brackets do not match in role assertion.");
					throw new SyntaxParsingException();
				}
				break;
			default:
				break;
			};

			if(done){
				break;
			}
		}
		r =  createRole(sub);
		return new RoleAssertion(a, b, r);
		
	}

	private static Assertion createConceptAssertion(LinkedList<String> in) throws SyntaxParsingException {
		if(in.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		Individual a = null;
		Concept c = null;
		int ind = 0, count =0;		
		boolean done = false;
		String tk = null;
		LinkedList<String> sub = new LinkedList<String>();
		while(!in.isEmpty()){
			tk = in.remove();
			if(tk.equals(LEFTDEL) ){
				count ++;
			}else if(tk.equals(RIGHTDEL)){
				count --;
			}
			switch (ind){
			case 0:
				a = Individual.newIndividual(tk);
				ind++;
				break;
			case 1:
				sub.add(tk);
				sub.addAll(in);
				done = true;
				break;
			default:
				break;
			}
			if(done){
				break;
			}
		}
		c =  conParser(sub);
		return new ConceptAssertion(a, c);
	}


	
	
	
	//deal with concepts only
	public static Concept conParser(LinkedList<String> tokens) throws SyntaxParsingException{
		if(tokens.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		Concept ret = null;
		int counter = 0;
		//remove outer brackets if any
		if(tokens.peek().equals(LEFTDEL)){
			tokens.remove();
			tokens.removeLast();
		}
		//cases
		String tk = tokens.getFirst();
		if(tk .equals( LEFTDEL)){
			counter ++;
			tokens.remove();
		}
		else if(tk.equals( RIGHTDEL)){
			counter --;
			tokens.remove();
		}
		else if(tk .equals(AND)){
			tokens.remove();
			if(tokens.isEmpty()){
				//we assume that (and) is top
				return CONCEPT_TOP;
			}
			ret = createAndConcept(tokens);
		}
		else if(tk .equals(SOME)){
			tokens.remove();
			ret = createExistsConcept(tokens);
		}
		else if(tk .equals(ALL)){
			tokens.remove();
			ret = createUniversalConcept(tokens);
		}
		else if(tk .equals(OR)){
			tokens.remove();
			if(tokens.isEmpty()){
				//we assume that (and) is top
				return CONCEPT_BOTTOM;
			}
			ret = createOrConcept(tokens);
		}
		else if(tk .equals(NOT)){
			tokens.remove();
			ret = createNotConcept(tokens);
		}
		else if(tk.equals(EQ)){
			//concrete domains
			tokens.remove();
			ret = createDomainConcept(tokens, 0);
		}
		else if(tk.equals(LESSTHAN)){
			tokens.remove();
			ret = createDomainConcept(tokens, -1);
		}
		else if(tk.equals(LESSEQUAL)){
			tokens.remove();
			ret =  createDomainConcept(tokens, 1);
		}
		//if no keywords found, then this is a PC
		else{
			ret = createPConcept(tk);
		}
		return ret;
	}
	
	//some R.C
	private static ExistsConcept createExistsConcept(LinkedList<String> in) throws SyntaxParsingException{
		if(in.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		Role r = null;
		Concept filler = null;
		int count = 0;
		LinkedList<String> sub = new LinkedList<String>();
		//get the role
		String tk = null;
		while(!in.isEmpty()){
			tk = in.peek();
			if(tk.equals(LEFTDEL) ){
				count++;
			}else if(tk.equals(RIGHTDEL)){
				count--;
			}
			sub.add(tk);
			in.remove();
			if(count == 0){
				//one concept found
				r = createRole(sub);
				sub.clear();
				break;
			}
		}
		//find the filler
		filler = conParser(in);
		return new ExistsConcept(r, filler);
	}
	
	//and (D) (D) etc.
	private static AndConcept createAndConcept(LinkedList<String> in) throws SyntaxParsingException{
		int count = 0;
		HashSet<Concept> cons = new HashSet<Concept>();
		LinkedList<String> sub = new LinkedList<String>();
		while(!in.isEmpty()){
			String tk = in.removeFirst();
			if(tk .equals(LEFTDEL)){
				count ++;
			}
			else if(tk .equals(RIGHTDEL)){
				count --;
			}
			sub.add(tk);
			if(count == 0){
				//one concept found
				cons.add(conParser(sub));
				sub.clear();
			}
		}
		return new AndConcept(cons);
	}
	
	private static PConcept createPConcept(String pc){
		return new PConcept(pc);
	}
	
	// (role r) only
	private static Role createRole(LinkedList<String> in) throws SyntaxParsingException{
		Role r = null;
		if(in.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		while(!in.isEmpty()){
			String tk = in.removeFirst();
			if(tk.equals(LEFTDEL)){
				in.removeLast();
			}
			else if(tk.equals(ROLE)){
				//the next would be a role or a role inclusion
				//sub.addAll(in);
				String rs = in.removeFirst();
				if(!in.isEmpty()){
					//syntax error, no role path supported yet
					System.out.println("The syntax for role is wrong. Extra chars ignored: "+rs);
				}
				r = new Role(rs);
				break;
			}
		}
		return r;
	}
	
	
	
	private static UniversalConcept createUniversalConcept(LinkedList<String> in) throws SyntaxParsingException{
		if(in.peek() == null){
			System.out.println("Empty list of tokens, please check!");
			throw new SyntaxParsingException();
		}
		Role r = null;
		Concept filler = null;
		int count = 0;
		LinkedList<String> sub = new LinkedList<String>();
		//get the role
		String tk = null;
		while(!in.isEmpty()){
			tk = in.peek();
			if(tk.equals(LEFTDEL) ){
				count++;
			}else if(tk.equals(RIGHTDEL)){
				count--;
			}
			sub.add(tk);
			in.remove();
			if(count == 0){
				//one concept found
				r = createRole(sub);
				sub.clear();
				break;
			}
		}
		//find the filler
		filler = conParser(in);
		return new UniversalConcept(r, filler);
	}
	
	/*
	 * The same as OR concepts
	 */
	private static OrConcept createOrConcept(LinkedList<String> in) throws SyntaxParsingException{
		int count = 0;
		HashSet<Concept> cons = new HashSet<Concept>();
		LinkedList<String> sub = new LinkedList<String>();
		while(!in.isEmpty()){
			String tk = in.removeFirst();
			if(tk.equals(LEFTDEL)){
				count ++;
			}
			else if(tk.equals(RIGHTDEL)){
				count --;
			}
			sub.add(tk);
			if(count == 0){
				//one concept found
				cons.add(conParser(sub));
				sub.clear();
			}
		}
		return new OrConcept(cons);
	}
	
	//(? (role r) "v"); 0 for =, -1 for <, 1 for > 
	private static DomainConcept<?,?> createDomainConcept(LinkedList<String> in, int type) throws SyntaxParsingException{
		Object lhs = null;
		Object rhs = null;
		DomainConcept<?, ?> dc = null;
		int count = 0;
		boolean left = true;
		LinkedList<String> sub = new LinkedList<String>();
		while(!in.isEmpty()){
			String tk = in.removeFirst();
			if(tk.equals(LEFTDEL)){
				count ++;
			}
			else if(tk.equals(RIGHTDEL)){
				count --;
			}
			sub.add(tk);
			if(count == 0){
				if(left){
					//get the left hand side
					lhs =  getStringOrRole(sub);
				}else{
					//the right hand side
					rhs = getStringOrRole(sub);
				}
				sub.clear();
				left = false;
			}
		}	
		//get a list of constants
		if(lhs instanceof String){
			Util.addKBConstants((String)lhs);
		}
		if(rhs instanceof String){
			Util.addKBConstants((String)rhs);
		}
		
		switch (type){
		case 0: dc = new DomainConceptEQ<Object, Object>(lhs, rhs); 
		//build the map
		//TBox.dcs.add(dc);
		break;
		case -1: dc = new DomainConceptLT<Object, Object>(lhs, rhs); break;
		case 1: dc = new DomainConceptLTEQ<Object, Object>(lhs, rhs); break;
		//default: dc = new DomainConcept(lhs, rhs); break;
		}
		return dc;
	}
	
	private static Object getStringOrRole(LinkedList<String> sub) throws SyntaxParsingException{
		Object ret = null;
		if(sub.size()>1){
			//not a string, a role
			ret = createRole(sub);
		}else if(sub.size() ==1){
			ret = (String)sub.iterator().next();
		}else{
			System.out.println("Error in syntax: only a role (role f) or just a string allowed in feature concepts!");
		}
		return ret;
	}
		
	private static NotConcept createNotConcept(LinkedList<String> in) throws SyntaxParsingException{
		return new NotConcept(conParser(in));
	}
	
	
	private static InclusionAxiom createInclusionAxiom(LinkedList<String> in) throws SyntaxParsingException{
		// DEBUG:
		StringBuffer ax = new StringBuffer();
		for (String s : in) {
			ax.append(s);
		}
		
		// :DEBUG
		Concept lhs = null, rhs = null;
		int count = 0;
		int sides = 2;
		LinkedList<String> sub = new LinkedList<String>();
		while(!in.isEmpty()){
			String tk = in.removeFirst();
			if(tk == null){
				System.out.println("Null concpet found in "+in);
				throw new SyntaxParsingException();
				//break;
			}
			if(sides <= 0){
				//extra tokens found after parsing LHS and RHS
				System.out.println("Concepts found after creating an inclusions: check the syntax of inclusions.");
				throw new SyntaxParsingException();
				//break;
			}
			if(tk.equals(LEFTDEL)){
				count ++;
			}
			else if(tk.equals(RIGHTDEL)){
				count --;
			}
			sub.add(tk);
			if(count == 0){
				//one concept found
				sides --;
				if(sides == 1){
					//assume LHS comes first
					lhs = conParser(sub);
				}else if(sides == 0){
					rhs = conParser(sub);
					
				}else {
					System.out.println("Wrong # of concepts found in inclusions: check the syntax.");
					throw new SyntaxParsingException();
				}
				sub.clear();
			}

		}
//		if(rhs == null){
//			System.err.println("not parsed count:"+sides + count);
//		}
		if (lhs == null || rhs == null) {
			System.err.println("WARNING! NULL AXIOM: " + rhs);
			
			throw new SyntaxParsingException();
		}
		return new InclusionAxiom(lhs, rhs);
	}
	
	//allows for A=B only, no A=B=C etc.
	private static EquivalenceAxiom createEqAxiom(LinkedList<String> in) throws SyntaxParsingException{
		Concept lhs = null, rhs = null;
		int count = 0;
		int sides = 2;
		LinkedList<String> sub = new LinkedList<String>();
		while(!in.isEmpty()){
			String tk = in.removeFirst();
			if(tk.equals(LEFTDEL)){
				count ++;
			}
			else if(tk.equals(RIGHTDEL)){
				count --;
			}
			sub.add(tk);
			if(count == 0){
				//one concept found
				sides --;
				if(sides == 1){
					//assume LHS comes first
					lhs = conParser(sub);
				}else if(sides == 0){
					rhs = conParser(sub);
				}else {
					System.err.println("Wrong # of concepts found in equivalent axioms: check the syntax.");
					throw new SyntaxParsingException();
				}
				sub.clear();
			}
		}
		return new EquivalenceAxiom(lhs, rhs);
	}
	
}
